#include <cassert>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <deque>
#include <systemc>
#include <vector>

using namespace sc_core;
using namespace sc_dt;
using namespace std;

template<typename T>
class QueueWriteIF : public sc_interface {
public:
    virtual void write(const T& value) = 0;
    virtual size_t num_free() = 0;
};

template<typename T>
class QueueReadIF : public sc_interface {
public:
    virtual void read(T& value) = 0;
    virtual size_t num_used() = 0;
};

template<typename T>
class QueueChannel1v1 : public QueueWriteIF<T>, public QueueReadIF<T>, public sc_prim_channel {
public:
    QueueChannel1v1(sc_module_name _name, size_t _max_size) : sc_prim_channel(_name), max_size(_max_size) {}
    size_t num_free() override {
        return max_size - buffer.size();
    }
    size_t num_used() override {
        return buffer.size();
    }
    void write(const T& value) override {
        while (this->num_free() == 0) {
            wait(revent);
        }
        buffer.push_back(value);
        wevent.notify(SC_ZERO_TIME);
    }
    void read(T& value) override {
        while (this->num_used() == 0) {
            wait(wevent);
        }
        value = buffer.front();
        buffer.pop_front();
        revent.notify(SC_ZERO_TIME);
    }
private:
    std::deque<T> buffer;
    size_t max_size;
    sc_event wevent;
    sc_event revent;
};

template<typename T, size_t N, size_t M>
class QueueChannelNvM : public QueueWriteIF<T>, public QueueReadIF<T>, public sc_prim_channel {
public:
    QueueChannelNvM(sc_module_name _name, size_t _max_size) 
        : sc_prim_channel(_name), max_size(_max_size), free_slots(_max_size), used_slots(0) {}
    size_t num_free() override {
        // Critical section
        mutex.lock();
        size_t size = max_size - buffer.size();
        mutex.unlock();
        return size;
    }
    size_t num_used() override {
        // Critical section
        mutex.lock();
        size_t size = buffer.size();
        mutex.unlock();
        return size;
    }
    void write(const T& value) override {
        // Request resource, wait if no free slot
        free_slots.wait();
        // Critical section
        mutex.lock();
        buffer.push_back(value);
        mutex.unlock();
        // Release resource, notify readers
        used_slots.post();
    }
    void read(T& value) override {
        // Request resource, wait if no used slot
        used_slots.wait();
        // Critical section
        mutex.lock();
        value = buffer.front();
        buffer.pop_front();
        mutex.unlock();
        // Release resource, notify writers
        free_slots.post();
    }
private:
    sc_mutex mutex;
    deque<T> buffer;
    size_t max_size;
    sc_semaphore free_slots;
    sc_semaphore used_slots;
};

SC_MODULE(Producer) {
    uint32_t count = 0;
    sc_port<QueueWriteIF<uint32_t>> write_port;
    SC_CTOR(Producer) {
        SC_THREAD(produce);
    }
    void produce() {
        while (true) {
            uint32_t value = count++;
            printf("@%s %s: WRITE Request\tnum_free=%ld, data=%d\n", sc_time_stamp().to_string().c_str(), this->name(), write_port->num_free(), value);
            write_port->write(value);
            printf("@%s %s: WRITE Response\tnum_free=%ld, data=%d\n", sc_time_stamp().to_string().c_str(), this->name(), write_port->num_free(), value);
            int wait_time = rand() % 4 + 1;
            printf("@%s %s: WAIT %d ns\n", sc_time_stamp().to_string().c_str(), this->name(), wait_time);
            wait(wait_time, SC_NS);
        }
    }
};

SC_MODULE(Consumer) {
    sc_port<QueueReadIF<uint32_t>> read_port;
    SC_CTOR(Consumer) {
        SC_THREAD(consume);
    }
    void consume() {
        while (true) {
            uint32_t value;
            printf("@%s %s: READ Request\tnum_used=%ld\n", sc_time_stamp().to_string().c_str(), this->name(), read_port->num_used());
            read_port->read(value);
            printf("@%s %s: READ Response\tnum_used=%ld, data=%d\n", sc_time_stamp().to_string().c_str(), this->name(), read_port->num_used(), value);
            int wait_time = rand() % 4 + 1;
            printf("@%s %s: WAIT %d ns\n", sc_time_stamp().to_string().c_str(), this->name(), wait_time);
            wait(wait_time, SC_NS);
        }
    }
};

class Top1v1 : public sc_module {
private:
    Producer producer;
    Consumer consumer;
    QueueChannel1v1<uint32_t> queue;

public:
    Top1v1(sc_module_name _name, size_t max_size) 
        : sc_module(_name), producer("producer"), consumer("consumer"), queue("queue", max_size) {
        producer.write_port(queue);
        consumer.read_port(queue);
        printf("Top1v1 constructor called\n");
    }
    ~Top1v1() {
        printf("Top1v1 destructor called\n");
    }
};

void test_1v1() {
/**
 ____________                           ________________                          ____________
 |          |    _____              ____|              |____             _____    |          |
 |          |----|   |              |   |              |   |             |   |----|          |
 | Producer |Port|WIF|---[write]--->|WIF| QueueChannel |RIF|---[read]--->|RIF|Port| Consumer |
 |          |----|___|              |___|              |___|             |___|----|          |
 |__________|                           |______________|                          |__________|
*/
    Top1v1 top {"top", 4};
    sc_start(50000, SC_NS);
}

template<size_t N, size_t M>
class TopNvM : public sc_module {
private:
    QueueChannelNvM<uint32_t, N, M> queue;
    Producer* producers[N];
    Consumer* consumers[M];
public:
    TopNvM(sc_module_name _name, size_t max_size) 
        : sc_module(_name), queue("queue", max_size) {
        for (size_t i = 0; i < N; ++i) {
            string name = "producer" + to_string(i);
            producers[i] = new Producer(name.c_str());
            producers[i]->write_port(queue);
        }
        for (size_t i = 0; i < M; ++i) {
            string name = "consumer" + to_string(i);
            consumers[i] = new Consumer(name.c_str());
            consumers[i]->read_port(queue);
        }
        printf("TopNvM constructor called\n");
    }
    ~TopNvM() {
        for (size_t i = 0; i < N; ++i) {
            delete producers[i];
        }
        for (size_t i = 0; i < M; ++i) {
            delete consumers[i];
        }
        printf("TopNvM destructor called\n");
    }
};

void test_NvM() {
/**
 ____________                           ________________                          ____________
 |          |    _____              ____|              |____             _____    |          |
 |          |----|   |              |   |              |   |             |   |----|          |
 | Producer |Port|WIF|---[write]--->|WIF| QueueChannel |RIF|---[read]--->|RIF|Port| Consumer |
 |          |----|___|              |___|              |___|             |___|----|          |
 |__________|                           |______________|                          |__________|
    (N producers)                        (1 channel)                          (M consumers)
*/
    const size_t N = 6;
    const size_t M = 4;
    TopNvM<N, M> top {"top", 4};
    sc_start(50000, SC_NS);
}

int sc_main(int argc, char **argv) {
    srand(0);
    if (argc < 2) {
        printf("Usage: %s <testcase>\n", argv[0]);
        printf("  testcase: 1v1 | NvM\n");
        return 1;
    }
    if (strcmp(argv[1], "1v1") == 0) {
        test_1v1();
    } 
    else if (strcmp(argv[1], "NvM") == 0) {
        test_NvM();
    } 
    else {
        printf("Invalid testcase: %s\n", argv[1]);
        return 1;
    }
    return 0;
}
